/**
 * @(#)EnlaceHibernateClasico.java Mar 2, 2007
 *
 * Copyright (c) 2005-2010 Psiqueware S.A. de C.V. All Rights Reserved.
 * This software is the proprietary information Psiqueware S.A. de C.V.
 * Use is subject to license terms.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * <br/>Usuario : AngelElectrico
 * <br/>Creaci&oacute;n: Mar 2, 2007 - 9:02:56 AM
 * <br/>
 * <br/>
 * <br/><t5>Cambios :</t5>
 * <br/><b>Fecha | Autor  |  Cambios</b>
 * <ul>
 * <li>Mar 2, 2007 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Creaci&oacute;n del Objeto.</li>
 * <li>Mar 06, 2007 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Se le cambi&oacute; el nombre para reflejar mejor lo que hace el objeto..</li>
 * </ul>
 */

package com.psiqueware.alheli.dao.hibernate;

import com.psiqueware.alheli.basico.hilos.AdministradorHilos;
import com.psiqueware.alheli.dao.EnlaceTransaccional;
import com.psiqueware.alheli.dao.TipoDAO;
import com.psiqueware.alheli.exception.NullArgumentException;
import com.psiqueware.alheli.exception.NullPostConditionException;
import com.psiqueware.alheli.logging.Logger;
import com.psiqueware.alheli.logging.LoggerFactory;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.Transaction;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;


/**
 * {@link EnlaceTransaccional} para DAOs que utilizen Hibernate2.
 * <br/>Paquete Original: com.psiqueware.basico.dao.hibernate
 * <br/>Nombre  Original: EnlaceTransaccionalHibernate
 * <br/>Oprima para ver : <a href="EnlaceHibernateClasico.java.html"><i>C&oacute;digo Fuente</i> </a>
 *
 * @author <a href="mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a>
 * @version Revision: 1.1 Mar 2, 2007 9:02:56 AM
 */
public final class EnlaceHibernateClasico implements EnlaceTransaccional, Runnable
{

    /**
     * Nombre de la clase que implementa el patr&oacute;n 'transaction per request'.
     */
    public static final String s_CLASE_TRX_PER_REQUEST_FILTER = "EnlaceTransaccionalFilter";

    /**
     * Identificador &uacute;nico del {@link EnlaceTransaccional}.
     */
    private static final AtomicLong s_idEnlace = new AtomicLong(0);

    /**
     * Logger de clase para EnlaceHibernateClasico
     */
    private static final Logger s_log = LoggerFactory.getLogger(EnlaceHibernateClasico.class);

    /**
     * Bandera que indica que el {@link EnlaceTransaccional} tiene una transacci&oacute;n abierta.
     */
    private final AtomicBoolean m_vivo = new AtomicBoolean(false);

    /**
     * {@link Transaction} hecha con este {@link EnlaceTransaccional}.
     */
    private Transaction m_transaction;

    /**
     * N&uacute;mero de Transacciones realizadas.
     */
    private int m_noTransacciones;

    /**
     * Nombre del Enlace
     */
    private String m_nombreEnlace = "EnlaceHC" + s_idEnlace.getAndIncrement();

    /**
     * Sesion utilizada durante este enlace.
     */
    private Session m_session;

    /**
     * Lugar de donde se inici&oacute; la transacci&oacute;n.
     */
    private String m_stackTraceElement;

    /**
     * Bandera que indica que el stackTraceElement debe buscar un nuevo origen ya que se detect&oacute; un patr&oacute;n 'transaction per request'.
     * http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch02.html#session-per-request
     */
    private boolean m_buscarNuevoOrigen = false;

    /**
     * Constructor de EnlaceHibernateClasico.
     */
    public EnlaceHibernateClasico()
    {
    }

    /**
     * Devuelve el {@link TipoDAO} que debe utilizar este {@link EnlaceTransaccional}.
     *
     * @return el {@link TipoDAO} para este {@link EnlaceTransaccional}.
     */
    public TipoDAO getTipo()
    {
        return TipoDAO.HIBERNATE_CLASICO;
    }

    /**
     * Inicia una transaccion.
     */
    public void iniciaTransaccion()
    {
        m_session = HibernateServiceLocator.getSession();
        if (null == m_session)
            throw new NullPostConditionException("<error><nombre>" + m_nombreEnlace + "</nombre><msg>No se pudo generar una sesi&oacute;n desde Hibernate.</msg></error>");
        m_session.setFlushMode(FlushMode.COMMIT);
        m_session.setCacheMode(CacheMode.IGNORE);
        m_transaction = m_session.beginTransaction();
        if (null == m_transaction)
            throw new NullPostConditionException("<error><nombre>" + m_nombreEnlace + "</nombre><msg>No se pudo iniciar una transacci&oacute;n desde Hibernate.<msg></error>");
        m_transaction.begin();
        if (!m_transaction.isActive())
            throw new RuntimeException("La transacci&oacute;n no se inici&oacute; para: " + m_nombreEnlace);

        if (s_log.isDebugEnabled())
        {
            final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            if (stackTrace.length > 2)
            {
                m_stackTraceElement = stackTrace[2].toString();
                if (m_stackTraceElement.contains(s_CLASE_TRX_PER_REQUEST_FILTER)) m_buscarNuevoOrigen = true;
            }
        }
        else
        {
            m_stackTraceElement = "Habilite el nivel de Debug para activar esta funci&oacute;n.";
        }

        if (s_log.isDebugEnabled())
            s_log.debug("<msg>" + m_nombreEnlace + " ha iniciado una transaccion.</msg><origen>" + m_stackTraceElement + "<origen>");

        //Si estamos debugeando llevamos un registro del tiempo de vida del enlace.
        if (s_log.isDebugEnabled())
        {
            AdministradorHilos.lanzarDemonio(m_nombreEnlace, this);
            m_vivo.set(true);
        }
    }

    /**
     * Devuelve el objeto del cual estamos haciendo nuestra transaccion.
     *
     * @return Object para hacerle casting dependiendo nuestra capa de datos.
     */
    public Object continuaTransaccion()
    {
        m_noTransacciones++;
        if (m_buscarNuevoOrigen && s_log.isDebugEnabled())
        {
            final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            if (stackTrace.length > 6) m_stackTraceElement = stackTrace[6].toString();
            else m_stackTraceElement = stackTrace[stackTrace.length - 1].toString();
            m_buscarNuevoOrigen = false;
        }

        Session salida = null;
        if (null == m_transaction)
            throw new NullArgumentException("<error><nombre>" + m_nombreEnlace + "</nombre><msg>La transacci&oacute;n no hab&iacute;a sido iniciada.</msg><origen>" + m_stackTraceElement + "<origen></error>");
        if (!m_transaction.wasCommitted() && !m_transaction.wasRolledBack())
        {
            if (!m_transaction.isActive())
            {
                iniciaTransaccion();
                if (s_log.isDebugEnabled())
                    s_log.debug("<msg>" + m_nombreEnlace + " su sessi&oacute;n no se hab&iacute;a iniciado correctamente. Reiniciada.</msg><origen>" + m_stackTraceElement + "<origen>");
            }
            salida = m_session;
        }
        else
        {
            s_log.error("<error><nombre>" + m_nombreEnlace + "</nombre><msg>Se esta continuando una Transaccion cerrada.</msg><origen>" + m_stackTraceElement + "<origen></error>");
        }
        return salida;
    }

    /**
     * Finaliza la transaccion, en este punto ya no se puede hacer rollback.
     */
    public void finalizaTransaccion()
    {
        if (s_log.isDebugEnabled() && m_noTransacciones > 0)
            s_log.debug("<msg>" + m_nombreEnlace + " pretende ingresar las " + m_noTransacciones + " transacciones.</msg>");
        if (!m_transaction.wasRolledBack())
        {
            m_transaction.commit();
        }

        HibernateServiceLocator.closeSession(m_session);
        if (s_log.isDebugEnabled())
        {
            if (m_noTransacciones == 0)
            {
                s_log.debug("<msg>" + m_nombreEnlace + " cerr&oacute; sesi&oacute;n sin transacciones.</msg><origen>" + m_stackTraceElement + "<origen>");
            }
            else
            {
                s_log.debug("<msg>" + m_nombreEnlace + " tuvo &eacute;xito al finalizar las " + m_noTransacciones + " transacciones.</msg><origen>" + m_stackTraceElement + "<origen>");
            }
            m_vivo.set(false);
        }
    }

    /**
     * Invoquese si sucede un problema con la transaccion.
     */
    public void rollBack()
    {
        if (m_transaction.wasCommitted())
            throw new RuntimeException("<error><nombre>" + m_nombreEnlace + "- La Transaccion ya fu&eacute; ingresada a la Base de Datos. El Rollback no deber&iacute;a suceder.</msg></error>");
        //no es necesario hacerlo 2 veces
        if (m_session.isOpen() && !m_transaction.wasRolledBack())
        {
            if (s_log.isDebugEnabled())
                s_log.debug("<msg>" + m_nombreEnlace + "- La transacci&oacute;n fue regresada (rollback).</msg><origen>" + m_stackTraceElement + "<origen>");
            m_transaction.rollback();
            m_vivo.set(false);
        }
    }

    /**
     * Devuelve el n&uacute;mero de transacciones que se llevan realizadas por este {@link EnlaceTransaccional}.
     * Generalmente la implementaci&oacute;n implica contar el n&uacute;mero de veces que se ha llamado a continuaTransaccion.
     *
     * @return n&uacute;mero de transacciones.
     *
     * @see this#continuaTransaccion()
     */
    public int numeroTransacciones()
    {
        return m_noTransacciones;
    }


    /**
     * When an object implementing interface {@link Runnable} is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p/>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    public void run()
    {
        //Le damos un m&aacute;ximo de 30 minutos a la transaccion para seguir viva.
        long treintaMinutos = 30 * 60;
        long cincoMinutos = 5 * 60;

        long segundosVivo = 0;
        long segundosDesdeUltimaTransaccion = 0;
        int noTransacciones = m_noTransacciones;
        long segundosDesdeUltimoLog = 0;
        while (m_vivo.get())
        {
            try
            {
                //Dormimos un segundo y revisamos
                Thread.sleep(1000);
                segundosVivo++;
                segundosDesdeUltimaTransaccion++;
                segundosDesdeUltimoLog++;
                if (noTransacciones != m_noTransacciones)
                {
                    segundosDesdeUltimaTransaccion = 0;
                    noTransacciones = m_noTransacciones;
                }

                if (180 == segundosDesdeUltimoLog)
                {
                    segundosDesdeUltimoLog = 0;
                    s_log.info("<msg>" + m_nombreEnlace + " vivo desde hace " + segundosVivo + " segs, &uacute;ltima transacci&oacute;n  de (" + m_noTransacciones + ") hace " + segundosDesdeUltimaTransaccion + " segs. </msg><origen>" + m_stackTraceElement + "</origen>");
                    m_buscarNuevoOrigen = true;//<-Tratamos de generar un seguimiento mas detallado.
                }

                if (m_noTransacciones == 0 && cincoMinutos < segundosVivo)
                {
                    s_log.error("<msg>" + m_nombreEnlace + " estuvo vivo y sin transacciones durante " + (cincoMinutos / 60) + " minutos. Regresando la transacci&oacute;n y cerrando la sesion.</msg><origen>" + m_stackTraceElement + "</origen>");
                    m_vivo.set(false);
                    rollBack();
                    HibernateServiceLocator.closeSession(m_session);
                }

                if (treintaMinutos < segundosVivo)
                {
                    s_log.error("<msg>" + m_nombreEnlace + " estuvo vivo durante " + (treintaMinutos / 60) + " minutos. Regresando la transacci&oacute;n y cerrando la sesion.</msg><origen>" + m_stackTraceElement + "</origen>");
                    m_vivo.set(false);
                    rollBack();
                    HibernateServiceLocator.closeSession(m_session);
                }
            }
            catch (InterruptedException ignored)
            {
            }
        }


    }

    /**
     * Devuelve el nombre del {@link EnlaceTransaccional}.
     *
     * @return valor de 'm_nombre'.
     */
    public String toString()
    {
        return m_nombreEnlace;
    }
}

